// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <stddef.h>
#include <stdint.h>

#include <algorithm>
#include <map>
#include <sstream>

#include "base/macros.h"
#include "media/base/stream_parser.h"
#include "media/base/stream_parser_buffer.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace media {

typedef StreamParser::TrackId TrackId;
typedef StreamParser::BufferQueue BufferQueue;

const int kEnd = -1;
const uint8_t kFakeData[] = { 0xFF };
const TrackId kAudioTrackId = 0;
const TrackId kVideoTrackId = 1;
const TrackId kTextTrackIdA = 2;
const TrackId kTextTrackIdB = 3;

static bool IsAudio(scoped_refptr<StreamParserBuffer> buffer)
{
    return buffer->type() == DemuxerStream::AUDIO;
}

static bool IsVideo(scoped_refptr<StreamParserBuffer> buffer)
{
    return buffer->type() == DemuxerStream::VIDEO;
}

static bool IsText(scoped_refptr<StreamParserBuffer> buffer)
{
    return buffer->type() == DemuxerStream::TEXT;
}

// Creates and appends a sequence of StreamParserBuffers to the provided
// |queue|. |decode_timestamps| determines the number of appended buffers and
// their sequence of decode timestamps; a |kEnd| timestamp indicates the
// end of the sequence and no buffer is appended for it. Each new buffer's
// type will be |type| with track ID set to |track_id|.
static void GenerateBuffers(const int* decode_timestamps,
    StreamParserBuffer::Type type,
    TrackId track_id,
    BufferQueue* queue)
{
    DCHECK(decode_timestamps);
    DCHECK(queue);
    DCHECK_NE(type, DemuxerStream::UNKNOWN);
    DCHECK_LE(type, DemuxerStream::TYPE_MAX);
    for (int i = 0; decode_timestamps[i] != kEnd; ++i) {
        scoped_refptr<StreamParserBuffer> buffer = StreamParserBuffer::CopyFrom(kFakeData, sizeof(kFakeData),
            true, type, track_id);
        buffer->SetDecodeTimestamp(
            DecodeTimestamp::FromMicroseconds(decode_timestamps[i]));
        queue->push_back(buffer);
    }
}

class StreamParserTest : public testing::Test {
protected:
    StreamParserTest() { }

    // Returns the number of buffers in |merged_buffers_| for which |predicate|
    // returns true.
    size_t CountMatchingMergedBuffers(
        bool (*predicate)(scoped_refptr<StreamParserBuffer> buffer))
    {
        return static_cast<size_t>(count_if(merged_buffers_.begin(),
            merged_buffers_.end(),
            predicate));
    }

    // Appends test audio buffers in the sequence described by |decode_timestamps|
    // to |audio_buffers_|. See GenerateBuffers() for |decode_timestamps| format.
    void GenerateAudioBuffers(const int* decode_timestamps)
    {
        GenerateBuffers(decode_timestamps, DemuxerStream::AUDIO, kAudioTrackId,
            &buffer_queue_map_[kAudioTrackId]);
    }

    // Appends test video buffers in the sequence described by |decode_timestamps|
    // to |video_buffers_|. See GenerateBuffers() for |decode_timestamps| format.
    void GenerateVideoBuffers(const int* decode_timestamps)
    {
        GenerateBuffers(decode_timestamps, DemuxerStream::VIDEO, kVideoTrackId,
            &buffer_queue_map_[kVideoTrackId]);
    }

    // Current tests only need up to two distinct text BufferQueues. This helper
    // conditionally appends buffers to the underlying |buffer_queue_map_| keyed
    // by the respective track ID. If |decode_timestamps_{a,b}|
    // is NULL, then the corresponding BufferQueue is not changed at all.
    // Note that key collision on map insertion does not replace the previous
    // value.
    void GenerateTextBuffers(const int* decode_timestamps_a,
        const int* decode_timestamps_b)
    {
        if (decode_timestamps_a) {
            GenerateBuffers(decode_timestamps_a, DemuxerStream::TEXT, kTextTrackIdA,
                &buffer_queue_map_[kTextTrackIdA]);
        }

        if (decode_timestamps_b) {
            GenerateBuffers(decode_timestamps_b, DemuxerStream::TEXT, kTextTrackIdB,
                &buffer_queue_map_[kTextTrackIdB]);
        }
    }

    // Returns a string that describes the sequence of buffers in
    // |merged_buffers_|. The string is a concatenation of space-delimited buffer
    // descriptors in the same sequence as |merged_buffers_|. Each descriptor is
    // the concatenation of
    // 1) a single character that describes the buffer's type(), e.g. A, V, or T
    //    for audio, video, or text, respectively
    // 2) the buffer's track_id()
    // 3) ":"
    // 4) the buffer's decode timestamp.
    // If |include_type_and_text_track| is false, then items 1, 2, and 3 are
    // not included in descriptors. This is useful when buffers with different
    // media types but the same decode timestamp are expected, and the exact
    // sequence of media types for the tying timestamps is not subject to
    // verification.
    std::string MergedBufferQueueString(bool include_type_and_text_track)
    {
        std::stringstream results_stream;
        for (BufferQueue::const_iterator itr = merged_buffers_.begin();
             itr != merged_buffers_.end();
             ++itr) {
            if (itr != merged_buffers_.begin())
                results_stream << " ";
            const StreamParserBuffer& buffer = *(itr->get());
            if (include_type_and_text_track) {
                switch (buffer.type()) {
                case DemuxerStream::AUDIO:
                    results_stream << "A";
                    break;
                case DemuxerStream::VIDEO:
                    results_stream << "V";
                    break;
                case DemuxerStream::TEXT:
                    results_stream << "T";

                    break;
                default:
                    NOTREACHED();
                }
                results_stream << buffer.track_id() << ":";
            }
            results_stream << buffer.GetDecodeTimestamp().InMicroseconds();
        }

        return results_stream.str();
    }

    // Verifies that MergeBufferQueues() of the current |audio_buffers_|,
    // |video_buffers_|, |text_map_|, and |merged_buffers_| returns true and
    // results in an updated |merged_buffers_| that matches expectation. The
    // expectation, specified in |expected|, is compared to the string resulting
    // from MergedBufferQueueString() (see comments for that method) with
    // |verify_type_and_text_track_sequence| passed. |merged_buffers_| is appended
    // to by the merge, and may be setup by the caller to have some pre-existing
    // buffers; it is both an input and output of this method.
    // Regardless of |verify_type_and_text_track_sequence|, the marginal number
    // of buffers of each type (audio, video, text) resulting from the merge is
    // also verified to match the number of buffers in |audio_buffers_|,
    // |video_buffers_|, and |text_map_|, respectively.
    void VerifyMergeSuccess(const std::string& expected,
        bool verify_type_and_text_track_sequence)
    {
        // |merged_buffers| may already have some buffers. Count them by type for
        // later inclusion in verification.
        size_t original_audio_in_merged = CountMatchingMergedBuffers(IsAudio);
        size_t original_video_in_merged = CountMatchingMergedBuffers(IsVideo);
        size_t original_text_in_merged = CountMatchingMergedBuffers(IsText);

        EXPECT_TRUE(MergeBufferQueues(buffer_queue_map_, &merged_buffers_));

        // Verify resulting contents of |merged_buffers| matches |expected|.
        EXPECT_EQ(expected,
            MergedBufferQueueString(verify_type_and_text_track_sequence));

        // Verify that the correct number of each type of buffer is in the merge
        // result.
        size_t audio_in_merged = CountMatchingMergedBuffers(IsAudio);
        size_t video_in_merged = CountMatchingMergedBuffers(IsVideo);
        size_t text_in_merged = CountMatchingMergedBuffers(IsText);

        EXPECT_GE(audio_in_merged, original_audio_in_merged);
        EXPECT_GE(video_in_merged, original_video_in_merged);
        EXPECT_GE(text_in_merged, original_text_in_merged);

        EXPECT_EQ(buffer_queue_map_[kAudioTrackId].size(),
            audio_in_merged - original_audio_in_merged);
        if (buffer_queue_map_[kAudioTrackId].empty())
            buffer_queue_map_.erase(kAudioTrackId);
        EXPECT_EQ(buffer_queue_map_[kVideoTrackId].size(),
            video_in_merged - original_video_in_merged);
        if (buffer_queue_map_[kVideoTrackId].empty())
            buffer_queue_map_.erase(kVideoTrackId);

        size_t expected_text_buffer_count = 0;
        expected_text_buffer_count += buffer_queue_map_[kTextTrackIdA].size();
        if (buffer_queue_map_[kTextTrackIdA].empty())
            buffer_queue_map_.erase(kTextTrackIdA);
        expected_text_buffer_count += buffer_queue_map_[kTextTrackIdB].size();
        if (buffer_queue_map_[kTextTrackIdB].empty())
            buffer_queue_map_.erase(kTextTrackIdB);
        EXPECT_EQ(expected_text_buffer_count,
            text_in_merged - original_text_in_merged);
    }

    // Verifies that MergeBufferQueues() of the current |buffer_queue_map_| and
    // |merged_buffers_| returns false.
    void VerifyMergeFailure()
    {
        EXPECT_FALSE(MergeBufferQueues(buffer_queue_map_, &merged_buffers_));
    }

    // Helper to allow tests to clear all the input BufferQueues (except
    // |merged_buffers_|) and the BufferQueueMap that are used in
    // VerifyMerge{Success/Failure}().
    void ClearBufferQueuesButKeepAnyMergedBuffers() { buffer_queue_map_.clear(); }

private:
    StreamParser::BufferQueueMap buffer_queue_map_;
    BufferQueue merged_buffers_;

    DISALLOW_COPY_AND_ASSIGN(StreamParserTest);
};

TEST_F(StreamParserTest, MergeBufferQueues_AllEmpty)
{
    std::string expected = "";
    VerifyMergeSuccess(expected, true);
}

TEST_F(StreamParserTest, MergeBufferQueues_SingleAudioBuffer)
{
    std::string expected = "A0:100";
    int audio_timestamps[] = { 100, kEnd };
    GenerateAudioBuffers(audio_timestamps);
    VerifyMergeSuccess(expected, true);
}

TEST_F(StreamParserTest, MergeBufferQueues_SingleVideoBuffer)
{
    std::string expected = "V1:100";
    int video_timestamps[] = { 100, kEnd };
    GenerateVideoBuffers(video_timestamps);
    VerifyMergeSuccess(expected, true);
}

TEST_F(StreamParserTest, MergeBufferQueues_SingleTextBuffer)
{
    std::string expected = "T2:100";
    int text_timestamps[] = { 100, kEnd };
    GenerateTextBuffers(text_timestamps, NULL);
    VerifyMergeSuccess(expected, true);
}

TEST_F(StreamParserTest, MergeBufferQueues_OverlappingAudioVideo)
{
    std::string expected = "A0:100 V1:101 V1:102 A0:103 A0:104 V1:105";
    int audio_timestamps[] = { 100, 103, 104, kEnd };
    GenerateAudioBuffers(audio_timestamps);
    int video_timestamps[] = { 101, 102, 105, kEnd };
    GenerateVideoBuffers(video_timestamps);
    VerifyMergeSuccess(expected, true);
}

TEST_F(StreamParserTest, MergeBufferQueues_OverlappingMultipleText)
{
    std::string expected = "T2:100 T2:101 T3:103 T2:104 T3:105 T3:106";
    int text_timestamps_a[] = { 100, 101, 104, kEnd };
    int text_timestamps_b[] = { 103, 105, 106, kEnd };
    GenerateTextBuffers(text_timestamps_a, text_timestamps_b);
    VerifyMergeSuccess(expected, true);
}

TEST_F(StreamParserTest, MergeBufferQueues_OverlappingAudioVideoText)
{
    std::string expected = "A0:100 V1:101 T2:102 V1:103 T3:104 A0:105 V1:106 "
                           "T2:107";
    int audio_timestamps[] = { 100, 105, kEnd };
    GenerateAudioBuffers(audio_timestamps);
    int video_timestamps[] = { 101, 103, 106, kEnd };
    GenerateVideoBuffers(video_timestamps);
    int text_timestamps_a[] = { 102, 107, kEnd };
    int text_timestamps_b[] = { 104, kEnd };
    GenerateTextBuffers(text_timestamps_a, text_timestamps_b);
    VerifyMergeSuccess(expected, true);
}

TEST_F(StreamParserTest, MergeBufferQueues_NonDecreasingNoCrossMediaDuplicate)
{
    std::string expected = "A0:100 A0:100 A0:100 V1:101 V1:101 V1:101 A0:102 "
                           "V1:103 V1:103";
    int audio_timestamps[] = { 100, 100, 100, 102, kEnd };
    GenerateAudioBuffers(audio_timestamps);
    int video_timestamps[] = { 101, 101, 101, 103, 103, kEnd };
    GenerateVideoBuffers(video_timestamps);
    VerifyMergeSuccess(expected, true);
}

TEST_F(StreamParserTest, MergeBufferQueues_CrossStreamDuplicates)
{
    // Interface keeps the choice undefined of which stream's buffer wins the
    // selection when timestamps are tied. Verify at least the right number of
    // each kind of buffer results, and that buffers are in nondecreasing order.
    std::string expected = "100 100 100 100 100 100 102 102 102 102 102 102 102";
    int audio_timestamps[] = { 100, 100, 100, 102, kEnd };
    GenerateAudioBuffers(audio_timestamps);
    int video_timestamps[] = { 100, 100, 102, 102, 102, kEnd };
    GenerateVideoBuffers(video_timestamps);
    int text_timestamps[] = { 100, 102, 102, 102, kEnd };
    GenerateTextBuffers(text_timestamps, NULL);
    VerifyMergeSuccess(expected, false);
}

TEST_F(StreamParserTest, MergeBufferQueues_InvalidDecreasingSingleStream)
{
    int audio_timestamps[] = { 101, 102, 100, 103, kEnd };
    GenerateAudioBuffers(audio_timestamps);
    VerifyMergeFailure();
}

TEST_F(StreamParserTest, MergeBufferQueues_InvalidDecreasingMultipleStreams)
{
    int audio_timestamps[] = { 101, 102, 100, 103, kEnd };
    GenerateAudioBuffers(audio_timestamps);
    int video_timestamps[] = { 104, 100, kEnd };
    GenerateVideoBuffers(video_timestamps);
    VerifyMergeFailure();
}

TEST_F(StreamParserTest, MergeBufferQueues_ValidAppendToExistingMerge)
{
    std::string expected = "A0:100 V1:101 T2:102 V1:103 T3:104 A0:105 V1:106 "
                           "T2:107";
    int audio_timestamps[] = { 100, 105, kEnd };
    GenerateAudioBuffers(audio_timestamps);
    int video_timestamps[] = { 101, 103, 106, kEnd };
    GenerateVideoBuffers(video_timestamps);
    int text_timestamps_a[] = { 102, 107, kEnd };
    int text_timestamps_b[] = { 104, kEnd };
    GenerateTextBuffers(text_timestamps_a, text_timestamps_b);
    VerifyMergeSuccess(expected, true);

    ClearBufferQueuesButKeepAnyMergedBuffers();

    expected = "A0:100 V1:101 T2:102 V1:103 T3:104 A0:105 V1:106 T2:107 "
               "A0:107 V1:111 T2:112 V1:113 T3:114 A0:115 V1:116 T2:117";
    int more_audio_timestamps[] = { 107, 115, kEnd };
    GenerateAudioBuffers(more_audio_timestamps);
    int more_video_timestamps[] = { 111, 113, 116, kEnd };
    GenerateVideoBuffers(more_video_timestamps);
    int more_text_timestamps_a[] = { 112, 117, kEnd };
    int more_text_timestamps_b[] = { 114, kEnd };
    GenerateTextBuffers(more_text_timestamps_a, more_text_timestamps_b);
    VerifyMergeSuccess(expected, true);
}

TEST_F(StreamParserTest, MergeBufferQueues_InvalidAppendToExistingMerge)
{
    std::string expected = "A0:100 V1:101 T2:102 V1:103 T3:104 A0:105 V1:106 "
                           "T2:107";
    int audio_timestamps[] = { 100, 105, kEnd };
    GenerateAudioBuffers(audio_timestamps);
    int video_timestamps[] = { 101, 103, 106, kEnd };
    GenerateVideoBuffers(video_timestamps);
    int text_timestamps_a[] = { 102, 107, kEnd };
    int text_timestamps_b[] = { 104, kEnd };
    GenerateTextBuffers(text_timestamps_a, text_timestamps_b);
    VerifyMergeSuccess(expected, true);

    // Appending empty buffers to pre-existing merge result should succeed and not
    // change the existing result.
    ClearBufferQueuesButKeepAnyMergedBuffers();
    VerifyMergeSuccess(expected, true);

    // But appending something with a lower timestamp than the last timestamp
    // in the pre-existing merge result should fail.
    int more_audio_timestamps[] = { 106, kEnd };
    GenerateAudioBuffers(more_audio_timestamps);
    VerifyMergeFailure();
}

} // namespace media
